#include "SPI2_Config.h"

/**
  ******************************************************************************
  * @brief  控制SPI2的片选信号(NSS)
  * @param  BitValue: 电平值（0:低电平选中，1:高电平取消选中）
  * @note   硬件特性：
  *         - 使用软件NSS模式（硬件NSS引脚未使用）
  *         - 典型时序要求：NSS下降沿到第一个SCK上升沿需>10ns
  *         - NSS上升沿后需保持至少50ns才能开始新传输
  ******************************************************************************
  */
void SPI2_NSS(u8 BitValue)
{
    GPIO_WriteBit(SPI2_CS1_PORT, SPI2_CS1_PIN, (BitAction)BitValue);
    /* 注：实际工程中建议添加延时确保信号稳定 */
    if(BitValue == 0) Delay_us(1); // 选中后延时1us满足tCSS时间
}

/**
  ******************************************************************************
  * @brief  反初始化SPI2外设
  * @note   安全措施：
  *         - 先禁用SPI再复位GPIO，避免总线冲突
  *         - 复位后所有相关寄存器恢复默认值
  ******************************************************************************
  */
void SPI2_Deinit(void)
{
    SPI_Cmd(SPI2, DISABLE);  // 先关闭SPI模块
    /* 复位SPI2配置寄存器 */
    SPI_I2S_DeInit(SPI2);
    /* 完全复位GPIO配置 */
    GPIO_DeInit(GPIOB);
}

/**
  ******************************************************************************
  * @brief  初始化SPI2外设
  * @param  CPHA: 时钟相位（SPI_CPHA_1Edge/SPI_CPHA_2Edge）
  * @param  CPOL: 时钟极性（SPI_CPOL_Low/SPI_CPOL_High）
  * @note   硬件配置细节：
  *         - SCK频率计算：PCLK1/(分频系数)
  *           STM32F103 PCLK1最大36MHz，4分频得9MHz SPI时钟
  *         - 输入模式选择：
  *           IPU(上拉输入)可避免MISO线浮空状态
  *         - 复用功能映射：
  *           PB13/14/15默认复用为SPI2，无需重映射
  ******************************************************************************
  */
void SPI2_Config(u16 CPHA, u16 CPOL)
{
    /* 时钟使能策略 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    /* NSS引脚配置（PB12）*/
    GPIO_InitTypeDef GPIO_InitStruct = {
        .GPIO_Pin   = GPIO_Pin_12,
        .GPIO_Mode  = GPIO_Mode_Out_PP,
        .GPIO_Speed = GPIO_Speed_50MHz  // 高速模式满足快速片选切换
    };
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    SPI2_NSS(1);  // 初始状态不选中设备

    /* SCK/MOSI配置（复用推挽输出）*/
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* MISO配置（上拉输入）*/
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;    // 内置上拉电阻约40kΩ
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* SPI参数配置 */
    SPI_InitTypeDef SPI_InitStruct = {
        .SPI_Direction = SPI_Direction_2Lines_FullDuplex,
        .SPI_Mode = SPI_Mode_Master,
        .SPI_DataSize = SPI_DataSize_8b,
        .SPI_CPOL = CPOL,
        .SPI_CPHA = CPHA,
        .SPI_NSS = SPI_NSS_Soft,       // 软件控制NSS
        .SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4,  // 9MHz @36MHz PCLK1
        .SPI_FirstBit = SPI_FirstBit_MSB,
        .SPI_CRCPolynomial = 7         // CRC未使用时保持默认
    };
    SPI_Init(SPI2, &SPI_InitStruct);

    /* 使能SPI前清除所有标志位 */
    SPI_I2S_ClearFlag(SPI2, SPI_I2S_FLAG_OVR);
    SPI_Cmd(SPI2, ENABLE);
}

/**
  ******************************************************************************
  * @brief  SPI2单字节交换（全双工通信）
  * @param  SendByte: 要发送的字节
  * @retval 接收到的字节（超时返回0xFF）
  * @note   关键时序：
  *         - 发送到接收的间隔时间取决于SCK频率
  *         - 典型超时计算：72MHz主频时1ms=72000个周期
  *         - 错误处理策略：
  *           1. 总线忙时直接返回错误
  *           2. 发送/接收超时返回错误
  ******************************************************************************
  */
u8 SPI2_SwapByte(u8 SendByte)
{
    volatile u32 timeout = SystemCoreClock / 1000;  // 1ms超时基准

    /* 总线状态检查 */
    if(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY))
        return 0xFF;

    /* 发送阶段 */
    while(!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) && (timeout-- > 0));
    if(timeout == 0) return 0xFF;
    SPI_I2S_SendData(SPI2, SendByte);

    /* 接收阶段 */
    timeout = SystemCoreClock / 1000;  // 重置超时计数器
    while(!SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) && (timeout-- > 0));
    if(timeout == 0) return 0xFF;

    /* 读取前清除OVR标志（防止先前溢出影响）*/
    SPI_I2S_ClearFlag(SPI2, SPI_I2S_FLAG_OVR);
    return SPI_I2S_ReceiveData(SPI2);
}

/**
  ******************************************************************************
  * @brief  发送指令+地址帧（标准SPI Flash操作格式）
  * @param  Instruction: 1字节指令码
  * @param  Address: 24位存储器地址
  * @retval 执行状态（true=成功，false=地址无效）
  * @note   Flash操作协议：
  *         1. 指令阶段：发送1字节操作码
  *         2. 地址阶段：大端格式发送3字节地址
  *         3. 数据阶段：根据指令可能有后续数据
  *         典型时序示例（读数据指令）：
  *         [0x03][A23-A16][A15-A8][A7-A0][Dout...]
  ******************************************************************************
  */
bool SPI2_SendBuffer(u8 Instruction, u32 Address)
{
    /* 地址有效性验证（24位地址空间） */
    if(Address & 0xFF000000) {
        USART1_Printf("Address overflow: 0x%08X", Address);
        return false;
    }

    /* 指令发送阶段 */
    SPI2_SwapByte(Instruction);

    /* 地址发送阶段（MSB First） */
    SPI2_SwapByte((Address >> 16) & 0xFF);  // 地址字节1
    SPI2_SwapByte((Address >> 8)  & 0xFF);  // 地址字节2
    SPI2_SwapByte( Address        & 0xFF);  // 地址字节3

    return true;
}

/************************* 扩展功能函数 ***************************/

/**
  ******************************************************************************
  * @brief  初始化SPI2为模式0（CPOL=0, CPHA=0）
  * @note   模式0特性：
  *         - 时钟空闲低电平
  *         - 数据在第一个边沿（上升沿）采样
  *         适用于大多数SPI Flash设备
  ******************************************************************************
  */
void SPI2_Init(void)
{
    SPI2_Config(SPI_CPHA_1Edge, SPI_CPOL_Low);
}

/**
  ******************************************************************************
  * @brief  使SPI2进入低功耗状态
  * @note   省电策略：
  *         1. 禁用SPI外设时钟
  *         2. 保持IO口状态不变
  *         典型电流从mA级降至μA级
  ******************************************************************************
  */
void SPI2_Sleep(void)
{
    SPI_Cmd(SPI2, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, DISABLE);
}

/**
  ******************************************************************************
  * @brief  启动SPI传输（带总线状态检查）
  * @note   安全机制：
  *         1. 等待总线空闲（防止冲突）
  *         2. NSS下降沿与SCK保持同步
  *         3. 满足tCSS建立时间要求
  ******************************************************************************
  */
void SPI2_Start(void)
{
    /* 等待总线空闲（最长等待1ms） */
    u32 timeout = SystemCoreClock / 1000;
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) && timeout--);
    
    /* 清除可能的错误标志 */
    SPI_I2S_ClearFlag(SPI2, SPI_I2S_FLAG_OVR);
    
    SPI2_NSS(0);  // 选中设备
    Delay_us(1);  // 满足tCSS时间
}

/**
  ******************************************************************************
  * @brief  停止SPI传输
  * @note   时序要求：
  *         1. 确保最后一位数据已传输完成
  *         2. NSS上升沿后保持tCSH时间
  ******************************************************************************
  */
void SPI2_Stop(void)
{
    /* 等待传输完成 */
    u32 timeout = SystemCoreClock / 1000;
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) && timeout--);
    
    SPI2_NSS(1);  // 取消选中
    Delay_us(1);  // 满足tCSH时间
}
